package de.dhbw.karlsruhe.picsim.rs232;

import de.dhbw.karlsruhe.picsim.model.io.ExternalReadOnly;
import de.dhbw.karlsruhe.picsim.util.Bits;
import de.dhbw.karlsruhe.picsim.util.impl.BitUtilities;
import de.dhbw.karlsruhe.picsim.util.impl.Lengths;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: user
 * Date: May 1, 2010
 * Time: 1:05:15 AM
 */
public final class PicViewProtocolImpl implements PicViewProtocol {
    private static final Log log = LogFactory.getLog(PicViewProtocolImpl.class);
    static final String END_OF_MESSAGE = "\r";
    @NonNls
    private static final String AN_NIPPLE = "[0-9:;<=>\\?]";
    public static final Pattern OUTPUT_PATTERN = Pattern.compile("^" + AN_NIPPLE + "{8}" + END_OF_MESSAGE + "\\z");
    private static final Pattern INPUT_PATTERN = Pattern.compile("^" + AN_NIPPLE + "{4}" + END_OF_MESSAGE + "\\z");

    @Nullable
    @Override
    public ExternalReadOnly getTheirPorts(final String message) {
        if (!INPUT_PATTERN.matcher(message).matches()) {
            log.error(message + ": is illegal");
            return null;
        }
        final byte[] bytes = message.getBytes();

        final Bits highPortA = BitUtilities.subBits(getBits(bytes[0]), 0, 1);
        final Bits lowPortA = getBits(bytes[1]);
        final Bits portA = BitUtilities.concat(lowPortA, highPortA);
        final Bits highPortB = getBits(bytes[2]);
        final Bits lowPortB = getBits(bytes[3]);
        final Bits portB = BitUtilities.concat(lowPortB, highPortB);
        return new ExternalReadOnly() {
            @NotNull
            @Override
            public Bits getPortA() {
                return portA;
            }

            @NotNull
            @Override
            public Bits getPortB() {
                return portB;
            }
        };


    }

    private Bits getBits(final byte aByte) {
        final int information = aByte & 0xF;
        return BitUtilities.asLeastSignificantBitFirst(information, 4);
    }

    @Nullable
    @Override
    public String getMessage(final Bits trisA, final Bits portA, final Bits trisB, final Bits portB) {

        if (erroneousArgs(trisA, portA, trisB, portB)) {
            return null;
        }

        final StringBuilder builder = new StringBuilder();
        writeData(builder, BitUtilities.subBits(trisA, 4, 5));
        writeData(builder, BitUtilities.subBits(trisA, 0, 4));
        writeData(builder, BitUtilities.subBits(portA, 4, 5));
        writeData(builder, BitUtilities.subBits(portA, 0, 4));
        appendData(trisB, builder);
        appendData(portB, builder);
        builder.append(END_OF_MESSAGE);

        final String result = builder.toString();
        log.debug("Constructed Message: " + result);

        return result;

    }

    private static boolean erroneousArgs(final Bits trisA, final Bits portA, final Bits trisB, final Bits portB) {
        if (trisA.getLength() != 5 || portA.getLength() != 5) {
            log.error("trisA.getLength() = " + trisA.getLength());
            log.error("portA.getLength() = " + portA.getLength());
            return true;
        }
        if (portB.getLength() != Lengths.FILE_LENGTH || trisB.getLength() != Lengths.FILE_LENGTH) {
            log.error("portB.getLength() = " + portB.getLength());
            log.error("trisB.getLength() = " + trisB.getLength());
            return true;
        }
        return false;
    }

    private void appendData(final Bits data, final StringBuilder builder) {
        writeData(builder, BitUtilities.subBits(data, 4, 8));
        writeData(builder, BitUtilities.subBits(data, 0, 4));
    }

    private void writeData(final StringBuilder builder, final Bits bits) {
        final int value = BitUtilities.fromLeastSignificantBitFirst(bits);
        assert value <= 0xf;
        builder.appendCodePoint(value | 0x30);
    }


}
